package com.example.garbage;

/**
 * @Author Shunrai
 * @create 2024/3/13 14:08
 * @Version 1.0
 * @Description
 */
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.garbage.pojo.OdCostlMatrix;
import com.example.garbage.service.OdCostlMatrixService;
import com.google.ortools.Loader;
import com.google.ortools.constraintsolver.Assignment;
import com.google.ortools.constraintsolver.FirstSolutionStrategy;
import com.google.ortools.constraintsolver.LocalSearchMetaheuristic;
import com.google.ortools.constraintsolver.RoutingIndexManager;
import com.google.ortools.constraintsolver.RoutingModel;
import com.google.ortools.constraintsolver.RoutingSearchParameters;
import com.google.ortools.constraintsolver.main;
import com.google.protobuf.Duration;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Optional;
import java.util.logging.Logger;

@Component
/** Minimal VRP. */
public final class VrpCapacity {
    @Autowired
    private OdCostlMatrixService odCostlMatrixService;
    private static final Logger logger = Logger.getLogger(VrpCapacity.class.getName());

    @Component
    static class DataModel {

        public double[][] distanceMatrix=new double[88][88];


        //        public final long[] demands = {0, 1, 1, 2, 4, 2, 4, 8, 8, 1, 2, 1, 2, 4, 4, 8, 8};
        public long[] demands=new long[88];
        public final long[] vehicleCapacities = {3000, 3000, 3000};
        public final int vehicleNumber = 3;
        public final int depot = 0;
    }

    /// @brief Print the solution.
    static void printSolution(
            DataModel data, RoutingModel routing, RoutingIndexManager manager, Assignment solution) {
        // Solution cost.
        logger.info("Objective: " + solution.objectiveValue());
        // Inspect solution.
        long totalDistance = 0;
        long totalLoad = 0;
        for (int i = 0; i < data.vehicleNumber; ++i) {
            long index = routing.start(i);
            logger.info("Route for Vehicle " + i + ":");
            long routeDistance = 0;
            long routeLoad = 0;
            String route = "";
            while (!routing.isEnd(index)) {
                long nodeIndex = manager.indexToNode(index);
                routeLoad += data.demands[(int) nodeIndex];
                route += nodeIndex + " Load(" + routeLoad + ") -> ";
                long previousIndex = index;
                index = solution.value(routing.nextVar(index));
                routeDistance += routing.getArcCostForVehicle(previousIndex, index, i);
            }
            route += manager.indexToNode(routing.end(i));
            logger.info(route);
            logger.info("Distance of the route: " + routeDistance + "m");
            totalDistance += routeDistance;
            totalLoad += routeLoad;
        }
        logger.info("Total distance of all routes: " + totalDistance + "m");
        logger.info("Total load of all routes: " + totalLoad);
    }

    @Test
    public void main(String[] args) throws Exception {
        Loader.loadNativeLibraries();
        // Instantiate the data problem.
        final DataModel data = new DataModel();
        for (int i = 0; i < 88; i++) {
            data.demands[i]=120;
        }
        for (int i = 0; i < 83; i++) {
            List<OdCostlMatrix> oSite = odCostlMatrixService.list(new QueryWrapper<OdCostlMatrix>().eq("o_site_id", i+1));
            data.distanceMatrix[i]=oSite.stream().mapToDouble(v-> Optional.ofNullable(v.getCost()).orElse(0.0)).toArray();
        }
        int j=1;
        for (int i = 83; i <87 ; i++) {
            List<OdCostlMatrix> oSite = odCostlMatrixService.list(new QueryWrapper<OdCostlMatrix>().eq("o_station_id", j++));
            data.distanceMatrix[i]=oSite.stream().mapToDouble(v-> Optional.ofNullable(v.getCost()).orElse(0.0)).toArray();
        }
        for (int i = 87; i <88 ; i++) {
            List<OdCostlMatrix> oSite = odCostlMatrixService.list(new QueryWrapper<OdCostlMatrix>().eq("o_depot_id", 1));
            data.distanceMatrix[i]=oSite.stream().mapToDouble(v-> Optional.ofNullable(v.getCost()).orElse(0.0)).toArray();
        }
        System.out.println(data.distanceMatrix);
        // Create Routing Index Manager
        RoutingIndexManager manager =
                new RoutingIndexManager(data.distanceMatrix.length, data.vehicleNumber, data.depot);

        // Create Routing Model.
        RoutingModel routing = new RoutingModel(manager);

        // Create and register a transit callback.
        final int scale = 1000;
        final int transitCallbackIndex =
                routing.registerTransitCallback((long fromIndex, long toIndex) -> {
                    // Convert from routing variable Index to user NodeIndex.
                    int fromNode = manager.indexToNode(fromIndex);
                    int toNode = manager.indexToNode(toIndex);
                    return (long) (int) data.distanceMatrix[fromNode][toNode] *scale;
                });

        // Define cost of each arc.
        routing.setArcCostEvaluatorOfAllVehicles(transitCallbackIndex);

        // Add Capacity constraint.
        final int demandCallbackIndex = routing.registerUnaryTransitCallback((long fromIndex) -> {
            // Convert from routing variable Index to user NodeIndex.
            int fromNode = manager.indexToNode(fromIndex);
            return data.demands[fromNode];
        });
        routing.addDimensionWithVehicleCapacity(demandCallbackIndex, 0, // null capacity slack
                data.vehicleCapacities, // vehicle maximum capacities
                true, // start cumul to zero
                "Capacity");

        // Setting first solution heuristic.
        RoutingSearchParameters searchParameters =
                main.defaultRoutingSearchParameters()
                        .toBuilder()
                        .setFirstSolutionStrategy(FirstSolutionStrategy.Value.PATH_CHEAPEST_ARC)
                        .setLocalSearchMetaheuristic(LocalSearchMetaheuristic.Value.GUIDED_LOCAL_SEARCH)
                        .setTimeLimit(Duration.newBuilder().setSeconds(1).build())
                        .build();

        // Solve the problem.
        Assignment solution = routing.solveWithParameters(searchParameters);

        // Print solution on console.
        printSolution(data, routing, manager, solution);
    }

    private VrpCapacity() {}
}
